﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="HistoryRepository.cs" company="emrecoskun.net">
//   ecblog.codeplex.com - emrecoskun.net
// </copyright>
// <summary>
//   History repository
// </summary>
// --------------------------------------------------------------------------------------------------------------------
 
namespace EcBlog.Data.Sql.Base
{
    using System;
    using System.Data.Entity;
    using System.Linq;
    using System.Security.Principal;
    using System.Threading;

    using EcBlog.Data.Base;
    using EcBlog.Models;

    /// <summary>
    /// History repository
    /// </summary>
    /// <typeparam name="TEntity">The type of the entity.</typeparam>
    public class HistoryRepository<TEntity> : IHistoryRepository<TEntity> where TEntity : class
    {
        /// <summary>
        /// The context.
        /// </summary>
        private readonly TransactionalDbContext context;

        /// <summary>
        /// The entities db set.
        /// </summary>
        private readonly DbSet<TEntity> entitiesDbSet;

        /// <summary>
        /// Initializes a new instance of the <see cref="HistoryRepository&lt;TEntity&gt;"/> class.
        /// </summary>
        /// <param name="context">The context.</param>
        public HistoryRepository(TransactionalDbContext context)
        {
            this.context = context;
            this.entitiesDbSet = context.Set<TEntity>();
        }

        /// <summary>
        /// Gets the principal.
        /// </summary>
        protected IPrincipal Principal
        {
            get
            {
                return Thread.CurrentPrincipal;
            }
        }

        /// <summary>
        /// Gets the entity query specified include properties.
        /// </summary>
        /// <param name="includeProperties">The include properties.</param>
        /// <returns>The entity query</returns>
        public virtual IQueryable<TEntity> Get(string includeProperties = "")
        {
            IQueryable<TEntity> query = this.entitiesDbSet; 
            query = includeProperties.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                .Aggregate(query, (current, includeProperty) => current.Include(includeProperty)); 
            return query.AsNoTracking();
        }

        /// <summary>
        /// Inserts the specified get entity.
        /// </summary>
        /// <param name="getEntity">The get entity.</param>
        /// <param name="operation">The operation.</param>
        public virtual void Insert(Func<TEntity> getEntity, CrudOperation operation)
        {
            if (operation == CrudOperation.Create || operation == CrudOperation.Update)
            {
                this.context.OnSaveChanged
                    += (s, e)
                       =>
                           {
                               var entity = getEntity();
                               SetOperation(entity, operation);
                               SetLastUpdate(entity);
                               this.entitiesDbSet.Add(entity);
                           };
            }
            else
            {
                this.context.OnSaveChanging
                    += (s, e)
                       =>
                    {
                        var entity = getEntity();
                        SetOperation(entity, operation);
                        SetLastUpdate(entity);
                        this.entitiesDbSet.Add(entity);
                    };
            }
        }

        /// <summary>
        /// Gets the entity query with raw SQL.
        /// </summary>
        /// <param name="query">The query.</param>
        /// <param name="parameters">The parameters.</param>
        /// <returns>The entity query</returns>
        public virtual IQueryable<TEntity> GetWithRawSql(string query, params object[] parameters)
        {
            return this.entitiesDbSet.SqlQuery(query, parameters).AsNoTracking().AsQueryable();
        }

        /// <summary>
        /// Sets the operation.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <param name="operation">The operation.</param>
        private static void SetOperation(TEntity entity, CrudOperation operation)
        {
            var property = entity.GetType().GetProperty("Operation");
            if (property != null)
            {
                property.SetValue(entity, operation, null);
            }
        }

        private static void SetLastUpdate(TEntity entity)
        {
            var property = entity.GetType().GetProperty("LastModifiedUser");
            if (property != null)
            {
                property.SetValue(entity, Thread.CurrentPrincipal.Identity.Name, null);
            }

            property = entity.GetType().GetProperty("LastModified");
            if (property != null)
            {
                property.SetValue(entity, DateTime.Now, null);
            }
        }
    }
}